﻿

namespace BackupLib
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Linq;
    using System.Text;
    using System.IO;
    using System.ComponentModel;
    using Targets;

    public class BackupTask : INotifyPropertyChanged, IBackupTask
    {
        private string _sourceRoot;
        public string SourceRoot
        {
            get { return _sourceRoot; }
            set { _sourceRoot = value; NotifyPropertyChanged("SourceRoot"); }
        }

        private string _targetRoot;
        public string TargetRoot
        {
            get { return _targetRoot; }
            set { _targetRoot = value; NotifyPropertyChanged("TargetRoot"); }
        }

        private string _currentTargetRoot;
        public string CurrentTargetRoot
        {
            get { return _currentTargetRoot; }
            set { _currentTargetRoot = value; NotifyPropertyChanged("CurrentTargetRoot"); }
        }

        private string _lastTargetRoot;
        public string LastTargetRoot
        {
            get { return _lastTargetRoot; }
            set { _lastTargetRoot = value; NotifyPropertyChanged("LastTargetRoot"); }
        }

        /*        private bool _targetSupportsHardlinks;
                public bool TargetSupportsHardlinks
                {
                    get { return _targetSupportsHardlinks; }
                    set { _targetSupportsHardlinks = value; NotifyPropertyChanged("TargetSupportsHardlinks"); }
                }

                private bool _targetSupportsLongNames;
                public bool TargetSupportsLongNames
                {
                    get { return _targetSupportsLongNames; }
                    set { _targetSupportsLongNames = value; NotifyPropertyChanged("TargetSupportsLongNames"); }
                }
                 private bool _targetSupportsACL;
                public bool TargetSupportsACL
                {
                    get { return _targetSupportsACL; }
                    set { _targetSupportsACL = value; NotifyPropertyChanged("TargetSupportsACL"); }
                }

                private bool _targetSupportsADS;
                public bool TargetSupportsADS
                {
                    get { return _targetSupportsADS; }
                    set { _targetSupportsADS = value; NotifyPropertyChanged("TargetSupportsADS"); }
                }
         */

        private bool _copyACL;
        public bool CopyACL
        {
            get { return _copyACL; }
            set { _copyACL = value; NotifyPropertyChanged("CopyACL"); }
        }

        private bool _copyADS;
        public bool CopyADS
        {
            get { return _copyADS; }
            set { _copyADS = value; NotifyPropertyChanged("CopyADS"); }
        }

        private bool _fullBackup;
        public bool FullBackup
        {
            get { return _fullBackup; }
            set { _fullBackup = value; NotifyPropertyChanged("FullBackup"); }
        }

        private bool _recursive;
        public bool Recursive
        {
            get { return _recursive; }
            set { _recursive = value; NotifyPropertyChanged("Recursive"); }
        }

        private ObservableCollection<string> _excludes;
        public ObservableCollection<string> Excludes
        {
            get { return _excludes; }
            set { _excludes = value; NotifyPropertyChanged("Excludes"); }
        }

        private ObservableCollection<string> _includes;
        public ObservableCollection<string> Includes
        {
            get { return _includes; }
            set { _includes = value; NotifyPropertyChanged("Includes"); }
        }

        private DateTime _executionStart;
        public DateTime ExecutionStart
        {
            get { return _executionStart; }
            set { _executionStart = value; NotifyPropertyChanged("ExecutionStart"); }
        }

        //TODO: do not serialize
        private ObservableCollection<string> _failed;
        public ObservableCollection<string> Failed
        {
            get { return _failed; }
            set { _failed = value; NotifyPropertyChanged("Failed"); }
        }

        public string ExcludeString { 
            get { 
                StringBuilder sb = new StringBuilder();
                foreach (string exclude in Excludes)
                {
                    sb.Append(exclude);
                    sb.Append("; ");
                }
                return sb.ToString();
            } 
        }

        public string IncludeString
        {
            get
            {
                StringBuilder sb = new StringBuilder();
                foreach (string include in Includes)
                {
                    sb.Append(include);
                    sb.Append("; ");
                }
                return sb.ToString();
            }
        }
        
        public bool ExcludeHidden { get; set; }
        public bool ExcludeSystem { get; set; }
        
        public long? MinSize { get; set; }
        public long? MaxSize { get; set; }

        public DateTime? MinCreated { get; set; }
        public DateTime? MaxCreated { get; set; }
        public DateTime? MinChanged { get; set; }
        public DateTime? MaxChanged { get; set; }

        public event EventHandler<BackupFileEventArgs> OnFile;
        public event EventHandler<BackupEventArgs> OnStart;
        public event EventHandler<BackupFinishedEventArgs> OnStop;

        private IBackupTarget backupTarget { get; set; }
        private int cntFailed=0;
        private int cntSuccess=0;
        private int cntIgnored=0;
        private int cntDirs=0;

        private void PrepareBackup()
        {
            if (FullBackup)
            {
                LastTargetRoot = CurrentTargetRoot;
                DateTime jetzt = ExecutionStart;
                string timepath = jetzt.ToString("yyyy-MM-dd_HH-mm");
                CurrentTargetRoot = TargetRoot + timepath + Path.DirectorySeparatorChar;
            }
            else
            {
                CurrentTargetRoot = TargetRoot;
            }

            backupTarget = TargetFactory.GetBackupTarget(CurrentTargetRoot);
            backupTarget.CopyACL = CopyACL;
            backupTarget.CopyADS = CopyADS;
            backupTarget.CurrentTargetRoot = CurrentTargetRoot;
            backupTarget.LastTargetRoot = LastTargetRoot;
            backupTarget.SourceRoot = SourceRoot;
            
            backupTarget.CreateDirectory(CurrentTargetRoot);
        }

        public ObservableCollection<string> DoBackup()
        {
            if (OnStart != null)
                OnStart(this, new BackupEventArgs());

            PrepareBackup();

            Stack<string> stackDir = new Stack<string>();
            if (String.IsNullOrEmpty(SourceRoot))
            {
                foreach (string include in Includes)
                {
                    try
                    {
                        backupTarget.BackupFile(include);
                    }
                    catch (Exception)
                    {
                        Failed.Add(include);
                    }
                }
            }
            else
            {
                // Add initial directory.
                stackDir.Push(SourceRoot);
            }

            // Continue while there are directories to process
            while (stackDir.Count > 0)
            {
                // Get top directory
                string dir = stackDir.Pop();
                cntDirs++;

                foreach (FileAttrib file in APIWrapper.GetFiles(dir, "*.*"))
                {
                    if (file.Directory)
                    {
                        // Add all directories at this directory.
                        if (Recursive)
                        {
                            stackDir.Push(file.Name);
                        }
                    }
                    else
                    {
                        string relativeFilePath = PathUtil.RelativePathTo(SourceRoot, file.Name);
                        if (MatchesCriteria(relativeFilePath,file))
                        {
                            bool hasChanged = file.Archive;
                            BackupFileEventArgs fileEvent=null;
                            try
                            {
                                if (FullBackup)
                                {
                                    backupTarget.BackupFile(relativeFilePath, !hasChanged);
                                    cntSuccess++;
                                    file.Archive = false;
                                }
                                else if (hasChanged)
                                {
                                    //differenz backup
                                    backupTarget.BackupFile(relativeFilePath);
                                    cntSuccess++;
                                    file.Archive = false;
                                }
                                else
                                {
                                    cntIgnored++;
                                }
                            }
                            catch (Exception ex)
                            {
                                Failed.Add(file.Name);
                                fileEvent=new BackupFileEventArgs(){Failed=true, Message="Fehler beim Sichern", Details=ex.Message, Path=file.Name};
                                cntFailed++;
                            }
                            //update cache
                            //int hash = file.GetHashCode();

                            //notify
                            if (OnFile != null)
                            {
                                if (fileEvent==null)fileEvent=new BackupFileEventArgs();
                                fileEvent.Progress = (double)cntDirs/(cntDirs+stackDir.Count);
                                OnFile(this, fileEvent);
                            }

                            //System.Threading.Thread.Sleep(2000);
                        }
                    }
                }
            }
            if (OnStop != null)
                OnStop(this, new BackupFinishedEventArgs(){Failed=cntFailed,Ignored=cntIgnored,Successful=cntSuccess});
            return Failed;
        }

        public bool IsProcessing
        {
            get; set;
        }

        public void ReloadProject()
        {
        }

        public void Pause()
        {
            throw new NotImplementedException();
        }

        public void Stop()
        {
            throw new NotImplementedException();
        }


        internal bool MatchesCriteria(string file)
        {
            throw new NotImplementedException();
        }
        internal bool MatchesCriteria(string file, FileAttrib fa)
        {

            foreach (string exclude in Excludes)
            {
                if (file.Contains(exclude)) return false;
            }

            if (Includes.Count != 0)
            {
                bool matchesInclude = false;
                foreach (string include in Includes)
                {
                    if (file.Contains(include))
                    {
                        matchesInclude = true;
                        break;
                    }
                }
                if (!matchesInclude) return false;
            }

            if (ExcludeHidden && fa.Hidden)
            {
                return false;
            }
            
            if (ExcludeSystem && fa.System)
            {
                return false;
            }

            if (!ValidateValue(fa.Length, MinSize, MaxSize)) return false;
            if (!ValidateValue(fa.CreationTime, MinCreated, MaxCreated)) return false;
            if (!ValidateValue(fa.LastWrite, MinChanged, MaxChanged)) return false;
      
            return true;
        }

        private bool ValidateValue(IComparable o, IComparable min, IComparable max)
        {
            if (min != null)
            {
                if (o.CompareTo(min)<0) return false;
            }
            if (max != null)
            {
                if (o.CompareTo(max)>0) return false;
            }
            return true;
        }

        /*public static List<int> GetFilesRecursive(string b)
        {
            // 1.
            // Store results in the file results list.
            List<int> result = new List<int>();

            // 2.
            // Store a stack of our directories.
            Stack<string> stack = new Stack<string>();

            // 3.
            // Add initial directory.
            stack.Push(b);

            // 4.
            // Continue while there are directories to process
            while (stack.Count > 0)
            {
                // A.
                // Get top directory
                string dir = stack.Pop();

                try
                {
                    // B
                    // Add all files at this directory to the result List.
                    foreach (string file in Directory.GetFiles(dir, "*.*"))
                    {
                        int hash = file.GetHashCode();
                        if (result.Contains(hash)) throw new Exception("");
                        result.Add(hash);
                    }

                    // C
                    // Add all directories at this directory.
                    foreach (string dn in Directory.GetDirectories(dir))
                    {
                        stack.Push(dn);
                    }
                }
                catch
                {
                    // D
                    // Could not open the directory
                }
            }
            return result;
        }*/

        public BackupTask()
        {
            Excludes = new ObservableCollection<string>();
            Includes = new ObservableCollection<string>();
            Failed = new ObservableCollection<string>();
        }

        #region INotifyPropertyChanged Member

        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(string property)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(property));
            }            
        }                    

        #endregion
    }
}
